/**
 * Created on 10 avr. 08 by Sebastian Audet
 */
package grid;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Scanner;
import java.util.TreeSet;

import tile.Tile;
import tile.TileSet;

/**
 * @author Sebastian Audet
 * 
 */
public abstract class AbstractGrid implements Grid {

    //[y][x] //TODO Check for (x,y) bugs
    protected Node[][] nodes;
    protected GridSize size;

    public abstract int returnGridType();

    AbstractGrid() {
	size = new GridSize(10, 10);
	cinit();
    }

    public AbstractGrid(int width, int height) {
	size = new GridSize(width, height);
	cinit();
    }

    private void cinit() {
	nodes = new Node[size.y][size.x];
	for (int i = 0; i < size.y; i++) {
	    for (int j = 0; j < size.x; j++) {
		nodes[i][j] = new GridNode(new Location(j,i));
	    }
	}
    }

    public AbstractGrid(int width, int height, File file, TileSet set) {
	this(width, height);
	try {
	    readInGrid(file, set);
	} catch (GridOutOfBounds e) {
	    e.printStackTrace();
	}
    }

    public void activate(boolean b, int x, int y) throws GridOutOfBounds {
	try {
	    nodes[x][y].setActive(b);
	} catch (ArrayIndexOutOfBoundsException e) {
	    throw new GridOutOfBounds(x, y);
	}
    }

    public void activate(boolean[][] activationmatrix) throws GridOutOfBounds {
	int i = 0;
	int j = 0;
	try {
	    for (i = 0; i < activationmatrix.length; i++) {
		for (j = 0; j < activationmatrix[i].length; j++) {
		    nodes[i][j].setActive(activationmatrix[i][j]);
		}
	    }
	} catch (ArrayIndexOutOfBoundsException e) {
	    throw new GridOutOfBounds(i, j);
	}
    }

    public void activate(ArrayList<ArrayList<Boolean>> activationmatrix)
	    throws GridOutOfBounds {
	int i = 0;
	int j = 0;
	try {
	    for (i = 0; i < activationmatrix.size(); i++) {
		for (j = 0; j < activationmatrix.get(i).size(); j++) {
		    nodes[i][j].setActive(activationmatrix.get(i).get(j)
			    .booleanValue());
		}
	    }
	} catch (ArrayIndexOutOfBoundsException e) {
	    throw new GridOutOfBounds(i, j);
	}
    }

    public boolean addTile(Tile tile, int x, int y) {
	return nodes[y][x].addTile(tile);
    }

    public int getColumns() {
	return size.x;
    }

    public Node[] getEmptyNeighborNodes(int level, Node node) {
	Collection<Node> nodes = getEmptyNeighborNodesCollection(level,node);
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Node[] getEmptyNeighborNodes(Node node) {
	Collection<Node> nodes = getEmptyNeighborNodesCollection(node);
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Collection<Node> getEmptyNeighborNodesCollection(int level, Node node) {
	Node[] neinodes = getNeighborNodes(node);
	ArrayList<Node> nodes = new ArrayList<Node>();
	for(Node n:neinodes)
	{
	    if(n.isEmpty(level))
	    {
		nodes.add(n);
	    }
	}
	return nodes;
    }

    public Collection<Node> getEmptyNeighborNodesCollection(Node node) {
	Node[] neinodes = getNeighborNodes(node);
	ArrayList<Node> nodes = new ArrayList<Node>();
	for(Node n:neinodes)
	{
	    if(n.isActive())
	    {
		nodes.add(n);
	    }
	}
	return nodes;
    }

    public Node[] getEmptyNodes() {
	Collection<Node> nodes = getEmptyNodesCollection();
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Node[] getEmptyNodes(int level) {
	Collection<Node> nodes = getEmptyNodesCollection(level);
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Collection<Node> getEmptyNodesCollection() {
	ArrayList<Node> nodes = new ArrayList<Node>();
	for(Node[] a:this.nodes)
	{
	    for(Node n:a)
	    {
        	    if(n.isActive())
        	    {
        		nodes.add(n);
        	    }
	    }
	}
	return nodes;
    }

    public Collection<Node> getEmptyNodesCollection(int level) {
	ArrayList<Node> nodes = new ArrayList<Node>();
	for(Node[] a:this.nodes)
	{
	    for(Node n:a)
	    {
        	    if(n.isActive()&&n.returnLevel(level)==null)
        	    {
        		nodes.add(n);
        	    }
	    }
	}
	return nodes;
    }

    /* (non-Javadoc)
     * @see grid.Grid#getTileLevel(int)
     */
    public Tile[][] getTileLevel(int level) {
	int y = getRows();
	int x = getColumns();
	Tile[][] c = new Tile[y][x];
	for(int i=0;i<y;i++)
	{
	    for(int j=0;j<x;j++)
	    {
		c[i][j]=nodes[i][j].returnLevel(level);
	    }
	}
	return c;
    }

    /* (non-Javadoc)
     * @see grid.Grid#getTileLevelCollection(int)
     */
    public Collection<Collection<Tile>> getTileLevelCollection(int level) {
	ArrayList<Collection<Tile>> c = new ArrayList<Collection<Tile>>();
	for(int i=0;i<nodes.length;i++)
	{
	    ArrayList<Tile> t = new ArrayList<Tile>();
	    for(int j=0;j<nodes[i].length;j++)
	    {
		t.add(nodes[i][j].returnLevel(level));
	    }
	    c.set(i, t);
	}
	return c;
    }

    public Node[] getGridNodes() {
	Collection<Node> nodes = getGridNodesCollection();
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Collection<Node> getGridNodesCollection() {
	Collection<Node> nodes = new TreeSet<Node>();
	for(Node[] a:this.nodes)
	{
	    for(Node n:a)
	    {
		nodes.add(n);
	    }
	}
	return nodes;
    }

    public Node[] getNeighborNodes(Node node)
    {
	Collection<Node> nodes = getNeighborNodesCollection(node);
	return nodes.toArray(new Node[nodes.size()]);
    }

    public abstract Collection<Node> getNeighborNodesCollection(Node node);

    public int getRows() {
	return size.y;
    }

    public void readInActivation(File file) throws GridOutOfBounds {

	// TODO Rewrite this sloppy implementation

	ArrayList<ArrayList<Boolean>> strings = new ArrayList<ArrayList<Boolean>>();

	/*
	 * Works on a file 
	 * 1 0 1 0 1 
	 * 0 1 0 1 1 
	 * 1 1 0 0 0
	 * or on
	 * 1,0,1,1,1,
	 * 0,1,0,1,1
	 * 1,1,1,0,0,
	 * or on 
	 * 0;1;0;1;1
	 * 1;1;1;1;0
	 * 0;0;1;1;1
	 * 1;1;1;1;1
	 */
	try {
	    Scanner reader = new Scanner(new BufferedReader(
		    new FileReader(file)));
	    reader.useDelimiter("\n");
	    ArrayList<String> lines = new ArrayList<String>();
	    while (reader.hasNext()) {
		lines.add(reader.nextLine());
	    }
	    reader.close();
	    String delim = " ";
	    if(lines.get(0).contains(";"))
	    {
		delim = ";";
	    } else if(lines.get(0).contains(",")) {
		delim = ",";
	    }
	    for (int i = 0; i < lines.size(); i++) {
		reader = new Scanner(new BufferedReader(new StringReader(lines
			.get(i))));
		reader.useDelimiter(delim);
		ArrayList<Boolean> temp = new ArrayList<Boolean>();
		while (reader.hasNext()) {
		    temp.add(myboolparser(reader.next()));
		}
		strings.add(temp);
	    }
	} catch (FileNotFoundException e) {
	    e.printStackTrace();
	}
	activate(strings);
    }

    private Boolean myboolparser(String booleanrep) {
	// TODO Make this robust
	if (booleanrep.equalsIgnoreCase("true")
		|| booleanrep.equalsIgnoreCase("1")) {
	    return new Boolean(true);
	} else {
	    return new Boolean(false);
	}
    }
    
    public void readInActivation(URL url) throws GridOutOfBounds{
        readInActivation(new File(url.getPath()));
    }
    
    public void readInActivation(String path) throws GridOutOfBounds{
        readInActivation(new File(path));
    }

    public boolean readInGrid(File file, TileSet set) throws GridOutOfBounds {

	// TODO Rewrite this sloppy implementation

	ArrayList<ArrayList<String>> strings = new ArrayList<ArrayList<String>>();

	int l = 0;
	int m = 0;

	/*
	 * Works on a file 
	 * FR GE TL OP HH 
	 * JJ OK PL FF TT 
	 * 04 1 0 0 0
	 * or on
	 * FR;GE;TL;OP;HH 
	 * JJ;OK;PL;FF;TT 
	 * 04;1;0;0;0
	 * or on
	 * FR,GE,TL,OP,HH 
	 * JJ,OK,PL,FF,TT 
	 * 04,1,0,0,0
	 */
	try {
	    Scanner reader = new Scanner(new BufferedReader(
		    new FileReader(file)));
	    reader.useDelimiter("\n");
	    ArrayList<String> lines = new ArrayList<String>();
	    while (reader.hasNext()) {
		lines.add(reader.nextLine());
	    }
	    reader.close();
	    String delim = " ";
	    if(lines.get(0).contains(";"))
	    {
		delim = ";";
	    } else if(lines.get(0).contains(",")) {
		delim = ",";
	    }
	    for (int i = 0; i < lines.size(); i++) {
		reader = new Scanner(new BufferedReader(new StringReader(lines
			.get(i))));
		reader.useDelimiter(delim);
		ArrayList<String> temp = new ArrayList<String>();
		while (reader.hasNext()) {
		    temp.add(reader.next());
		}
		strings.add(temp);
	    }
	    for (l = 0; l < strings.size(); l++) {
		for (m = 0; m < strings.get(l).size(); m++) {
		    String t = strings.get(l).get(m);
		    if(!t.equals("0"))
		    {
		    nodes[l][m].addTile(new Tile(strings.get(l).get(m), set));}
		}
	    }
	    return true;
	} catch (FileNotFoundException e) {
	    e.printStackTrace();
	} catch (ArrayIndexOutOfBoundsException e) {
	    throw new GridOutOfBounds(l, m);
	}
	return false;
    }

    public boolean readInGrid(URL url, TileSet set) throws GridOutOfBounds {
        return readInGrid(new File(url.getPath()), set);
    }
    
    public boolean readInGrid(String path, TileSet set) throws GridOutOfBounds {
        return readInGrid(new File(path), set);
    }
    
    public boolean removeTile(Tile tile, int x, int y) {
	return nodes[y][x].removeTile(tile);
    }

    public boolean removeTileLevel(int level) {
	boolean found = false;
	for(int i = 0;i<nodes.length;i++)
	{
	    for(int j = 0;j<nodes[i].length;j++)
	    {
		Node node = nodes[i][j];
		Tile temp = null;
		if((temp = node.returnLevel(level))!=null)
		{
		    found = true;
		    node.removeTile(temp);
		}
	    }
	}
	return found;
    }

    public void setColumns(int columns) {
	setGridSize(size.x,columns);
    }

    public void setGridSize(GridSize size) {
	setGridSize(size.x,size.y); //TODO Bugfix?
    }

    public void setGridSize(int rows, int columns) {
	Node[][] newnodes = new Node[columns][rows];
	    if(columns>getColumns())
	    {
		if(rows>getRows())
		{
		    for(int i = 0;i<nodes.length;i++)
		    {
			System.arraycopy(nodes[i], 0, newnodes[i], 0, nodes[i].length);
		    }
		}
		else
		{
		    for(int i = 0;i<nodes.length;i++)
		    {
			System.arraycopy(nodes[i], 0, newnodes[i], 0, newnodes[i].length);
		    }
		}
	    }
	    else
	    {
		if(rows>getRows())
		{
		    for(int i = 0;i<newnodes.length;i++)
		    {
			System.arraycopy(nodes[i], 0, newnodes[i], 0, nodes[i].length);
		    }
		}
		else
		{
		    for(int i = 0;i<newnodes.length;i++)
		    {
			System.arraycopy(nodes[i], 0, newnodes[i], 0, newnodes[i].length);
		    }
		}
	    }
	    nodes=newnodes;
    }

    public void setRows(int rows) {
	setGridSize(rows,size.y);
    }

    public Node getNode(Location node) throws GridOutOfBounds
    {
	return getNode(node.x,node.y);
    }
    
    public Node getNode(int x,int y) throws GridOutOfBounds
    {
	try {
	return nodes[y][x];
	} catch(ArrayIndexOutOfBoundsException e)
	{
	    throw new GridOutOfBounds(x,y);
	}
    }

    public Node[] getActiveNodes() {
	Collection<Node> nodes = getActiveNodesCollection();
	return nodes.toArray(new Node[nodes.size()]);
    }

    public Collection<Node> getActiveNodesCollection() {
	Collection<Node> nodes = getGridNodesCollection();
	Collection<Node> rnodes = new ArrayList<Node>();
	Iterator<Node> itr = nodes.iterator();
	while(itr.hasNext()) {
	    Node n = itr.next();
	    if(n.isActive())
	    {
		rnodes.add(n);
	    }
	}
	return rnodes;
    }
    
    public Node[] getInActiveNodes()
    {
	Collection<Node> nodes = getInActiveNodesCollection();
	return nodes.toArray(new Node[nodes.size()]);
    }
    
    public Collection<Node> getInActiveNodesCollection()
    {
	Collection<Node> nodes = getGridNodesCollection();
	Collection<Node> rnodes = new ArrayList<Node>();
	for(Node n:nodes)
	{
	    if(!n.isActive())
	    {
		rnodes.add(n);
	    }
	}
	return rnodes;
    }

    
    /* (non-Javadoc)
     * @see grid.Grid#moveTile(tile.Tile, grid.Node, grid.Node)
     */
    public boolean moveTile(Tile tile, Node originatingNode, Node receivingNode) {
	return moveTile(tile,originatingNode.getLocation(),receivingNode.getLocation());
    }

    /* (non-Javadoc)
     * @see grid.Grid#moveTile(tile.Tile, grid.Location, grid.Location)
     */
    public boolean moveTile(Tile tile, Location oloc, Location rloc) {
	boolean one = addTile(tile,rloc.x,rloc.y);
	boolean two = removeTile(tile,oloc.x,oloc.y);
	if(one && two)
	{
	    return true;
	}
	else
	{
	    if(two)
	    {
	    addTile(tile,oloc.x,oloc.y);}
	    if(one)
	    {
	    removeTile(tile,rloc.x,rloc.y);}
	    return false;
	}
    }
}
